import queue
from common import *
import time


class FileStreamCollect:
    def __init__(self, file_handle, max_length):
        self.fs_dict = dict()
        self.max_length = max_length
        self.file_handle = file_handle

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close_all()

    def create_fs(self, position):
        file_num = position // self.max_length
        return self.file_handle[file_num].create()

    def add_fs(self, position):
        file_num = position // self.max_length
        if file_num not in self.fs_dict:
            self.fs_dict[file_num] = self.create_fs(position)
        return self.fs_dict[file_num]

    def close_all(self):
        for k, v in self.fs_dict.items():
            v.close()


class FileStream:
    def __init__(self, path, start):
        self.__inner_lock__ = Lock()
        self.path = path
        self.fs = open(self.path, "ab+")
        self.start = start
        self.closed = False

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.get_fs().close()

    def append(self, data_bytes):
        with self.__inner_lock__:
            self.get_fs().seek(0, 2)
            self.get_fs().write(data_bytes)
            return self.get_fs().tell()

    def write(self, data_bytes, index):
        with self.__inner_lock__:
            self.get_fs().seek(index)
            self.get_fs().write(data_bytes)

    def read(self, index, size):
        with self.__inner_lock__:
            fs = self.get_fs()
            fs.seek(index)
            return fs.read(size)

    def read_position(self, position, size):
        return self.read(position-self.start, size)

    def tell(self):
        self.get_fs().seek(0, 2)
        return self.get_fs().tell()

    def get_fs(self):
        return self.fs

    def close(self):
        self.closed = True
        self.get_fs().close()


class FileOpt:
    def __init__(self, path, start=0):
        self.write_lock = Lock()
        self.path = path
        self.tell_index = 0
        self.start = start
        self.fs = dict()

    def append(self, data_bytes):
        old_tell = self.tell()
        self.tell_index = self.get_fs().append(data_bytes)
        return old_tell, self.tell_index

    def write(self, data_bytes, position):
        self.get_fs().write(data_bytes, position)

    def write_str(self, data, position=None, encode="utf-8"):
        data_bytes = data.encode(encode)
        return self.write(data_bytes, position)

    def read(self, index, size):
        self.get_fs().read(index, size)

    def read_position(self, position, size):
        return self.read(position-self.start, size)

    def tell(self):
        return self.get_fs().tell()

    def close(self):
        self.get_fs().close()

    def create(self):
        return self.get_fs()

    def get_fs(self):
        pid = os.getpid()
        if pid not in self.fs or self.fs[pid].closed:
            self.fs[pid] = FileStream(self.path, self.start)
        return self.fs[pid]

    def __del__(self):
        for k, v in self.fs.items():
            v.close()


class IndexBase:
    def __init__(self, path, group_name=None, single_file=65535, fmt=""):
        self.inner_lock = Lock()
        self.fmt = fmt
        self.index_len = struct.calcsize(self.fmt)
        self.single_file_max = single_file
        self.single_file = single_file * self.index_len
        self.current_handle = None
        self.file_handle = dict()
        self.index_dir = path
        self.encode = cfg.index_encode
        self.group_name = path if group_name is None else group_name
        self.group_name_bytes = self.group_name.encode(self.encode)
        self.__load_file__()
        TransactionData.load_rollback_file(self.group_name)

    def __load_file__(self):
        if not os.path.exists(self.index_dir):
            os.makedirs(self.index_dir)
        path = self.index_dir.encode(self.encode)
        init_stat = MemoryFileOpt.group_init_data(self.group_name_bytes, path, self.single_file, self.index_len)
        if init_stat > 1:
            self.__init_group_file__()

    def __init_group_file__(self):
        dirs = os.listdir(self.index_dir)
        for d in dirs:
            file_name = os.path.join(self.index_dir, d)
            if os.path.isfile(file_name) and file_name.endswith(".idx"):
                MemoryFileOpt.load_group_file(self.group_name_bytes, file_name.encode(self.encode))

    def edit(self, position, index_data):
        MemoryFileOpt.write_group(self.group_name_bytes, index_data, position)

    def pack_edit(self, position, *args):
        data_bytes = self.pack(args)
        self.edit(position, data_bytes)

    def read(self, position, size=None):
        if size is None:
            size = self.index_len
        return MemoryFileOpt.read_group(self.group_name_bytes, position, size)

    def unpack_read(self, position, size=None):
        read_bytes = self.read(position, size)
        return self.unpack(read_bytes)

    def append(self, index_data):
        return MemoryFileOpt.append_collect(self.group_name_bytes, index_data)

    # def append_split(self, index_data, index_len):
    #     return MemoryFileOpt.append_split(self.group_name_bytes, index_data, index_len, len(index_data))

    def get_pos(self):
        return MemoryFileOpt.group_tell(self.group_name_bytes)

    def index_edit(self, index, data, fmt, shift):
        data_byte = unpack(fmt, data)
        size = calcsize(shift)
        self.edit(index+size, data_byte)

    def pack(self, *args):
        return struct.pack(self.fmt, *args)

    def unpack(self, bytes_val):
        return unpack(self.fmt, bytes_val)

    def read_chars(self, char_p):
        return MemoryFileOpt.read_bytes(char_p, self.index_len)

    def get_index(self, index, size=None):
        byte_items = self.read(index, size)
        return self.unpack(byte_items)

    def get_group_tag(self):
        return MemoryFileOpt.group_head_tag(self.group_name.encode(self.encode))

    def read_next(self, pos=None):
        if pos is None:
            max_val, min_val, top_val, count_val = self.get_group_tag()
            pos = top_val
        return self.get_index(pos)


class DataBase:
    def __init__(self, path, group_name=None, max_length=65535, max_open_file=2, prefix=""):
        self.inner_lock = Lock()
        self.group_name = path if group_name is None else group_name
        self.max_open_file = max_open_file
        self.data_dir = path
        self.max_length = max_length
        self.encode = cfg.index_encode
        self.file_write = queue.Queue()
        self.file_handle = dict()
        self.prefix = prefix
        self.__load_file__()

    def __load_file__(self):
        if not os.path.exists(self.data_dir):
            os.makedirs(self.data_dir)
        for root, dirs, files in os.walk(self.data_dir):
            for file_name in files:
                last_num = len(self.file_handle)
                all_name = os.path.join(root, file_name)
                self.create_file(all_name, last_num)
            break

    def __init_file__(self):
        handle_len = len(self.file_handle.items())
        if self.max_open_file > handle_len:
            for i in range(handle_len, self.max_open_file):
                self.create_data_file()

    def create_file(self, file_name, last_num):
        file_opt = FileOpt(file_name, start=self.max_length * last_num)
        self.file_handle[last_num] = file_opt
        self.file_write.put(file_opt)

    def create_data_file(self):
        last_num = len(self.file_handle)
        file_name = "%s/%s%d.dat" % (self.data_dir, self.prefix, last_num)
        if not os.path.isfile(file_name):
            self.create_file(file_name, last_num)

    def __get_write_opt__(self, size):
        self.__init_file__()
        while True:
            if not self.file_write.empty():
                fs = self.file_write.get()
                if fs.tell() + size > self.max_length:
                    self.create_data_file()
                else:
                    return fs

    def read(self, position, size, fs=None):
        file_num = position // self.max_length
        if fs is None:
            return self.file_handle[file_num].read_position(position, size)
        return fs.read_position(position, size)

    def read_str(self, position, size, fs=None):
        read_bytes = self.read(position, size, fs)
        return str(read_bytes, self.encode)

    def append(self, data):
        with self.inner_lock:
            fs = self.__get_write_opt__(len(data))
            tell, tell_end = fs.append(data)
            self.file_write.put(fs)
            return tell + fs.start, tell_end + fs.start

    def close(self):
        for k, v in self.file_handle.items():
            v.close()

    def create_fs(self):
        fs = FileStreamCollect(self.file_handle, self.max_length)
        return fs


